from pymclevel import mclevel
import string

"""
NOTE:  All of the variable names assume X and Y are the horizontal axes,
and Z represents altitude
"""

def getChunkBounds(level):
    """
    Returns (minX, minY, maxX, maxY) of the chunk coordinates.
    Multiply by 16 to get world dimensions.
    """
    chunkCoords = list(level.allChunks)
    minX = chunkCoords[0][0]
    minY = chunkCoords[0][1]
    maxX = chunkCoords[0][0]
    maxY = chunkCoords[0][1]
    for (x, y) in chunkCoords:
        minX = min(minX, x)
        minY = min(minY, y)
        maxX = max(maxX, x)
        maxY = max(maxY, y)
    return (minX, minY, maxX, maxY)

def getWorldSize(chunkBounds):
    return ((chunkBounds[2] - chunkBounds[0] + 1)*16, (chunkBounds[3] - chunkBounds[1] + 1)*16, 128)

def getChunkCoordinates(x, y, chunkBounds):
    """
    Calculates the chunk coordinates given x, y world coordinates.
    Returns a tuple of the form (chunkX, chunkY, localX, localY)
    where chunk(X|Y) care the x and y coordinates of the chunk and local(X|Y)
    are the coordinates relative to the specified chunk.
    """
    return (x/16 + chunkBounds[0], y/16 + chunkBounds[1], x % 16, y % 16)

def getChunks(level):
    chunks = [(x,y,level.getChunk(x, y)) for (x,y) in level.allChunks]
    chunkMap = {}
    for (x, y, chunk) in chunks:
        chunkMap[(x,y)] = chunk
    return chunkMap

def bitmapToMesh(bitmap, dim, base, width, height, blockType):
    """
    Generates a mesh of quads that cover the provided
    mesh specifying solid faces in the given dimension
    and offset from the origin by the given "base" coordinate.
    If bitmap > 0, a clockwise polygon is produced.
    If bitmap < 0, a counterclockwise polygon is produced.
    Otherwise, no polygon is created.
    """
    iDim = (dim + 1) % 3
    jDim = (dim + 2) % 3
    quads = []
    for i in range(0, width):
        for j in range(0, height):
            #print("testing: " + str((i, j)))
            if(bitmap[j][i] == blockType or bitmap[j][i] == -1 * blockType):
                target = bitmap[j][i]
                # try to find the largest rectangle possible starting at (i, j)
                minP = (i, j)
                maxI = i
                maxJ = j
                # get the maximum horizontal span
                for i2 in range(i, width):
                    if(bitmap[j][i2] == target):
                        maxI = i2
                    else:
                        break
                # try to extrude the maximum horizontal span as far down as possible
                for j2 in range(j, height):
                    stop = False
                    for i2 in range(i, maxI + 1):
                        if(bitmap[j2][i2] != target):
                            stop = True
                    if stop:
                        break
                    else:
                        maxJ = j2
                maxP = (maxI + 1, maxJ + 1)
                # mark all of the tiles we cover with this polygon as empty (so we don't duplicate quads)
                for i2 in range(i, maxI + 1):
                    for j2 in range(j, maxJ + 1):
                        bitmap[j2][i2] = 0
                # create a quad for the rectangle
                minP = (minP[0] + base[iDim], minP[1] + base[jDim])
                maxP = (maxP[0] + base[iDim], maxP[1] + base[jDim])
                quad = [[0,0,0] for x in range(4)]
                quad[0][iDim] = minP[0]
                quad[0][jDim] = minP[1]
                quad[0][dim] = base[dim]
                
                quad[1][iDim] = minP[0]
                quad[1][jDim] = maxP[1]
                quad[1][dim] = base[dim]
                
                quad[2][iDim] = maxP[0]
                quad[2][jDim] = maxP[1]
                quad[2][dim] = base[dim]
                
                quad[3][iDim] = maxP[0]
                quad[3][jDim] = minP[1]
                quad[3][dim] = base[dim]
                if(target == -1 * blockType):
                    tmp = tuple(quad)
                    quad[0] = tmp[3]
                    quad[1] = tmp[2]
                    quad[2] = tmp[1]
                    quad[3] = tmp[0]
                quad.append(blockType)
                quads.append(quad)
    return quads

def levelToMesh(level, dim):
    """
    Generates a mesh of quads aligned along the given dimension
    """
    iDim = (dim + 1) % 3
    jDim = (dim + 2) % 3
    chunkBounds = getChunkBounds(level)
    offsetX = 16 * chunkBounds[0]
    offsetY = 16 * chunkBounds[1]
    quads = []
    worldSize = getWorldSize(chunkBounds)
    blocksFound = set()
    for d in range(-1, worldSize[dim]):
        print("finished: " + "xyz"[dim] + " = " + str(d))
        # keep a bitmap of visible block faces (non-zero values indicate visible faces)
        bitmap = [[0 for i in range(worldSize[iDim])] for j in range(worldSize[jDim])]
        for i in range(0, worldSize[iDim]):
            for j in range(0, worldSize[jDim]):
                # the point to query
                qp = [0,0,0]
                qp[dim] = d
                qp[iDim] = i
                qp[jDim] = j
                #blockAt(0 takes (x, z, y) coordinates
                curBlock = level.blockAt(qp[0] + offsetX, qp[2], qp[1] + offsetY)
                qp[dim] += 1
                nextBlock = level.blockAt(qp[0] + offsetX, qp[2], qp[1] + offsetY)
                # if the current block XOR the next block is air
                if(nextBlock != 0 and curBlock == 0):
                    bitmap[j][i] = -1 * nextBlock
                    blocksFound.add(nextBlock)
                if(curBlock != 0 and nextBlock == 0):
                    bitmap[j][i] = curBlock
                    blocksFound.add(curBlock)
        startPoint = [0,0,0]
        startPoint[dim] = d + 1
        for blocktype in blocksFound:
            quads.extend(bitmapToMesh(bitmap, dim, startPoint, worldSize[iDim], worldSize[jDim], blocktype))
    return quads

def meshToObj(mesh, filename):
    vNum = 1
    print("Writing OBJ file: " + repr(filename))
    outFile = file(filename, "w")
    # Texture coordinates are used to specify color (from palate.txt)
    outFile.write("vt 0 0\n") #black
    outFile.write("vt 2 0\n") #red
    outFile.write("vt 3 0\n") #green
    outFile.write("vt 4 0\n") #blue
    # 1 = wall, 11 = death wall, 41 = goal, 9 = checkpoint, 46 = spiky launcher
    blocktype_to_vt = {1:1, 11:2, 41:3, 9:4, 46:1} # these indeces MUST start at 1!!!
    for vface in mesh:
        for i in range(0, 4):
            # swap y and z dimensions
            tmp = vface[i][2]
            vface[i][2] = vface[i][1]
            vface[i][1] = tmp
            f = tuple(vface[i])
            vNum += 1
            outFile.write("v %i %i %i\n" % f)
        vertIndeces = [i + vNum - 4 for i in range(4)]
        outFile.write("f")
        for i in range(4):
            if(blocktype_to_vt.has_key(vface[4])):
                outFile.write(" %i/%i" % (vertIndeces[i], blocktype_to_vt[vface[4]], ))
            else:
                outFile.write(" %i/%i" % (vertIndeces[i], 0, ))
        outFile.write("\n")
    outFile.close()

def levelToRLECollisionMap(level):
    """
    Returns a 2d array of RLE map data:
    [[[(int tile_type, int tile_span)]]]
    """
    rleMap = []
    chunkBounds = getChunkBounds(level)
    (spanX, spanY, spanZ) = getWorldSize(chunkBounds)
    offsetX = 16 * chunkBounds[0]
    offsetY = 16 * chunkBounds[1]
    spanZ = 128
    for worldX in range(0, spanX):
        print("%f complete" % (worldX*1.0/spanX))
        xspanData = []
        for worldY in range(0, spanY):
            data = []
            last = [-1, -1]
            for worldZ in range(0, spanZ):
                x = worldX + offsetX;
                y = worldY + offsetY;
                z = worldZ
                block = level.blockAt(x, z, y)
                if(block == last[0]):
                    last[1] += 1
                else:
                    if(last[0] != -1):
                        data.append(tuple(last))
                    last = [block, 1]
            xspanData.append(data)
        rleMap.append(xspanData)
    return rleMap

def outputRLEMap(rleMap, filename):
    offset = 0
    xwidth = len(rleMap);
    ywidth = len(rleMap[0]);
    outFile = file(filename, "w")
    outFile.write(str(xwidth) + " " + str(ywidth) + " " + "\n");
    for xspan in rleMap:
        for zcolumn in xspan:
            for cell in zcolumn:
                outFile.write("%i %i " % cell)
            outFile.write("\n");
    outFile.close()

def getSpikyLauncherLocations():
    launchers = []
    chunkBounds = getChunkBounds(level)
    (spanX, spanY, spanZ) = getWorldSize(chunkBounds)
    offsetX = 16 * chunkBounds[0]
    offsetY = 16 * chunkBounds[1]
    spanZ = 128
    for worldX in range(0, spanX):
        xspanData = []
        for worldY in range(0, spanY):
            data = []
            last = [-1, -1]
            for worldZ in range(0, spanZ):
                x = worldX + offsetX
                y = worldY + offsetY
                z = worldZ
                block = level.blockAt(x, z, y)
                if(block == 46):
                    launchers.append((worldX + 0.5, worldZ + 0.5, worldY + 0.5))
    print("Found " + str(len(launchers)) + " spiky ball launchers")
    return launchers

if __name__=="__main__":
    FILENAME = "map"
    level = mclevel.fromFile(FILENAME)
    quads = levelToMesh(level, 0)
    quads.extend(levelToMesh(level, 1))
    quads.extend(levelToMesh(level, 2))
    meshToObj(quads, "map.obj")
    outputRLEMap(levelToRLECollisionMap(level), "map.col")
    print(getSpikyLauncherLocations());